/*
** Keyboard & Joystick Routines by Neil C. Obremski           [ Magenta's Maze ]
** 2017 Gibdon Moon Productions                   [ http://magsmaze.gibdon.com ]
**
** This is free and unencumbered software released into the public domain.
**
** Anyone is free to copy, modify, publish, use, compile, sell, or
** distribute this software, either in source code form or as a compiled
** binary, for any purpose, commercial or non-commercial, and by any
** means.
**
** In jurisdictions that recognize copyright laws, the author or authors
** of this software dedicate any and all copyright interest in the
** software to the public domain. We make this dedication for the benefit
** of the public at large and to the detriment of our heirs and
** successors. We intend this dedication to be an overt act of
** relinquishment in perpetuity of all present and future rights to this
** software under copyright law.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
** OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
** OTHER DEALINGS IN THE SOFTWARE.
**
** Tested w/ DOSBox 0.74, Microsoft C 5.10
 */
#ifndef __MAG_INP
#define __MAG_INP

#include <bios.h>		/* _bios_keybrd */
#include "mag-bios.h"

char JOY_B1 = 0;
char JOY_B2 = 0;
char JOY_B3 = 0;
char JOY_B4 = 0;

int JOY_X1 = 0;
int JOY_Y1 = 0;
int JOY_X2 = 0;
int JOY_Y2 = 0;

#define JOY_MAX 9999
#define JOY_STICK1 0x3
#define JOY_STICK2 0xC

int JOY_LO = 0;
int JOY_HI = 1;
int JOY_RANGE = 150;
int JOY_START = 50;
int JOY_STICK = 0;
int JOY_PRECISE = 1;

#define joystart(sticks) { JOY_STICK = (sticks); JOY_LO = 0; JOY_HI = 1; }

#define JOY_LEFT(x) (x <= 0)
#define JOY_RIGHT(x) (x > JOY_START)
#define JOY_UP(y) (y <= 0)
#define JOY_DOWN(y) (y > JOY_START)

int ACT = -1; /* current actions pending (-1 == need initialization) */

#define ACT_LEFT	0x0001
#define ACT_RIGHT	0x0002
#define ACT_UP		0x0004
#define ACT_DOWN	0x0008
#define ACT_USE		0x0010
#define ACT_ESC		0x0020

int action(char);
void eat(void);
void joyauto(void);
unsigned joystick(void);
int pause(void);

/* get all input as a bit array and reset buffers */
int action(char pause)
{
	int act;

	if (-1 == ACT) {
		/* slow down keyboard rate to prevent overfilling key buffer */
		typematic(TYPE_DELAY_SLOW, TYPE_REPEAT_SLOW);

		ACT = 0;
	}

	do {
		/* translate keyboard into actions */
		eat();
		act = ACT;

		/* translate joystick data into actions */
		if (JOY_STICK) {
			joystick();

			if (JOY_LEFT(JOY_X1)) {
				act |= ACT_LEFT;
			} else if (JOY_RIGHT(JOY_X1)) {
				act |= ACT_RIGHT;
			}

			if (JOY_UP(JOY_Y1)) {
				act |= ACT_UP;
			} else if (JOY_DOWN(JOY_Y1)) {
				act |= ACT_DOWN;
			}

			if (JOY_B2) {
				act |= ACT_USE;
			}

			if (JOY_B1) {
				act |= ACT_ESC;
			}
		}

	} while (pause && !act);

	/* reset global actions */
	ACT = 0;

	return act;
}

/* eat keyboard input */
void eat(void)
{
	while (kbhit()) {
		switch ((int)getch()) {
			case 0x00: /* arrow keys */
				switch ((int)getch()) {
					case 0x48: ACT |= ACT_UP; break;
					case 0x50: ACT |= ACT_DOWN; break;
					case 0x4B: ACT |= ACT_LEFT; break;
					case 0x4D: ACT |= ACT_RIGHT; break;
				}
				break;
			case 'w': case 'W': case '8': ACT |= ACT_UP; break;
			case 's': case 'S': case '2': ACT |= ACT_DOWN; break;
			case 'a': case 'A': case '4': ACT |= ACT_LEFT; break;
			case 'd': case 'D': case '6': ACT |= ACT_RIGHT; break;
			case 0xD: case ' ': case 'y': case 'Y': ACT |= ACT_USE; break;
			case 0x1B:case 'q': case 'n': case 'N': ACT |= ACT_ESC; break;
		}
	}
}

/* autodetect joysticks */
void joyauto(void)
{
	joystart(JOY_STICK1 | JOY_STICK2);

	if (JOY_MAX == joystick()) {
		joystart(JOY_STICK1);
		if (JOY_MAX == joystick()) {
			joystart(0); /* no joysticks */
		}
	}
}

/* reads joystick data into globals */
unsigned joystick(void)
{
	unsigned i = 0, bits, axes, precise = JOY_PRECISE;

	if (!JOY_STICK) { /* must call joystart() to enable joysticks first */
		return JOY_MAX;
	}

	JOY_X1 = JOY_Y1 = JOY_X2 = JOY_Y2 = 0 - JOY_START;

	/* use local (not global) so an interrupt can't change it for this call */
	if (precise) {
		_disable(); /* CLI */
	}

	outp(0x201, 0); /* clear joystick port (sets all axis bits to 1) */

	do {
		bits = inp(0x201);
		axes = bits;

		/* each axis goes from 1 to 0 depending on how far stick is pushed */
		JOY_X1 += (axes & 0x1);
		axes >>= 1;
		JOY_Y1 += (axes & 0x1);
		axes >>= 1;
		JOY_X2 += (axes & 0x1);
		axes >>= 1;
		JOY_Y2 += (axes & 0x1);

	} while ((bits & JOY_STICK) && ++i < JOY_MAX);

	if (precise) {
		_enable(); /* STI */
	}

	if (!JOY_LO) {
		/* shoddy auto-calibration intended mostly for DOSbox users */
		JOY_LO = (int)(i - 1);
		JOY_HI = i + (i >> 1) + (i >> 2) + (i >> 3) - 1;

		/* adjust axes based on lo/hi */
		JOY_RANGE = JOY_HI - JOY_LO;
		JOY_START = (JOY_RANGE >> 1) + (JOY_RANGE >> 3);

		/* joystick not detected */
		if (JOY_LO <= 0 || JOY_HI <= 0 || JOY_RANGE <= 0) {
			JOY_LO = 0;
			JOY_HI = 1;
			return JOY_MAX;
		}

	} else {
		/* update stick maximums */
		if (i > JOY_HI) {
			JOY_HI = i;

			/* adjust axes based on lo/hi */
			JOY_RANGE = JOY_HI - JOY_LO;
			JOY_START = (JOY_RANGE >> 1) + (JOY_RANGE >> 3);
		}
	}


	/* update buttons (bit ON when resting, bit OFF when pushed) */
	JOY_B1 = (char)(bits & 0x10 ? 0 : 1);
	JOY_B2 = (char)(bits & 0x20 ? 0 : 1);
	JOY_B3 = (char)(bits & 0x40 ? 0 : 1);
	JOY_B4 = (char)(bits & 0x80 ? 0 : 1);

	return i;
}

int pause(void)
{
	int act;
	do {
		act = action(1);
	} while (!(act & (ACT_USE | ACT_ESC)));
	return act;
}

#endif
